/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.tedu.mall.common.webapi.service.impl;

import cn.tedu.mall.common.config.CoolSharkStaticPrefixConfiguration;
import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.common.service.ISmsService;
import cn.tedu.mall.common.utils.AliSmsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@DubboService
@Service
@Slf4j
public class SmsServiceImpl implements ISmsService {
    //处理字符串code,使用string序列化的redistemplate对象
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private AliSmsUtils aliSmsUtils;

    @Override
    public void sendCode(String phone) throws Exception {
        validationCodeSending(phone);
        String code = generateCode(phone);
        aliSmsUtils.sendCode(phone, code);
    }

    @Override
    public String getCode(String phone) throws Exception {
        String codeKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.RAND_CODE_PREFIX + phone;
        String lockKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.PHONE_SMS_LOCK_PREFIX + phone;
        if (stringRedisTemplate.hasKey(lockKey)) {
            log.info("您的号码被锁定");
            throw new CoolSharkServiceException(ResponseCode.CONFLICT, "您的号码被锁定");
        }
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        String code = opsForValue.get(codeKey);
        if (StringUtils.isEmpty(code)) {
            log.info("你的电话号码没有发送过验证码");
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "你的电话号码没有发送过验证码");
        }
        return code;
    }

    public String generateCode(String phone) {
        //生成code 保存key值 发送验证码
        String code = (new Random().nextInt(8999) + 1000) + "";
        String codeKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.RAND_CODE_PREFIX + phone;
        stringRedisTemplate.opsForValue().set(codeKey, code, aliSmsUtils.getExpiration(), TimeUnit.MILLISECONDS);
        return code;
    }

    public void validationCodeSending(String phone) {
        //检验正则
        validatePhone(phone);
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        //引入分布式锁.
        String distributedLock = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.CODE_DISTRIBUTED_LOCK_PREFIX + phone;
        Boolean enable = opsForValue.setIfAbsent(distributedLock, "");
        if (!enable) {
            log.info("您的验证码发送频率过高");
            throw new CoolSharkServiceException(ResponseCode.CONFLICT, "您的验证码发送频率过高");
        }

        //生成key值
        String codeKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.RAND_CODE_PREFIX + phone;
        String lockKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.PHONE_SMS_LOCK_PREFIX + phone;
        String limitKey = CoolSharkStaticPrefixConfiguration.SmsPrefixConfiguration.CODE_SEND_LIMIT_PREFIX + phone;
        //生成当前系统时间戳
        Long nowTimeStamp = LocalDateTime.now().atZone(ZoneId.of("+8")).toInstant().toEpochMilli();
        //查看锁存在是否
        Boolean lockExist = stringRedisTemplate.hasKey(lockKey);
        if (lockExist) {
            stringRedisTemplate.delete(distributedLock);
            log.info("您的手机已经被锁定10分钟,请10分钟之后重试");
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "您的手机已经被锁定10分钟,请10分钟之后重试");
        }
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();

        Long size = opsForList.size(limitKey);
        if (size > aliSmsUtils.getLimitPerMin()) {
            //清空数据,锁住phone
            Set deleteKeys = new HashSet<>();
            deleteKeys.add(codeKey);
            deleteKeys.add(limitKey);
            deleteKeys.add(distributedLock);
            stringRedisTemplate.delete(deleteKeys);
            opsForValue.set(lockKey, "", aliSmsUtils.getExpiration(), TimeUnit.MILLISECONDS);
            log.info("您发送验证码太频繁了");
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "您发送验证码太频繁了");
        } else if (size == aliSmsUtils.getLimitPerMin()) {
            //这时候要检查最初的验证码发送
            String firstTimeStampStr = opsForList.rightPop(limitKey);
            Long firstTimeStamp;
            try {
                firstTimeStamp = Long.parseLong(firstTimeStampStr);
            } catch (NumberFormatException e) {
                log.debug("限制验证码发送数量的值不是时间戳:{}" + e.getMessage());
                Set deleteKeys = new HashSet<>();
                deleteKeys.add(codeKey);
                deleteKeys.add(limitKey);
                deleteKeys.add(distributedLock);
                stringRedisTemplate.delete(deleteKeys);
                opsForValue.set(lockKey, "", aliSmsUtils.getExpiration(), TimeUnit.MILLISECONDS);
                log.info("发送验证码系统异常,手机号被锁定10分钟,请debug调试");
                throw new CoolSharkServiceException(ResponseCode.INTERNAL_SERVER_ERROR, "发送验证码系统异常,手机号被锁定10分钟,请debug调试");
            }
            //检查一分钟
            Long timeGap = nowTimeStamp - firstTimeStamp;
            if (timeGap < 1000 * 60) {
                //说明最先发送的验证码时间和当前在1分钟之内
                Set deleteKeys = new HashSet<>();
                deleteKeys.add(codeKey);
                deleteKeys.add(limitKey);
                deleteKeys.add(distributedLock);
                stringRedisTemplate.delete(deleteKeys);
                opsForValue.set(lockKey, "", aliSmsUtils.getExpiration(), TimeUnit.MILLISECONDS);
                log.info("1分钟内超过限定次数发送验证码,手机号锁定10分钟");
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "1分钟内超过限定次数发送验证码,手机号锁定10分钟");
            }
        } else {
            opsForList.leftPush(limitKey, nowTimeStamp + "");
            stringRedisTemplate.delete(distributedLock);
        }
    }

    private void validatePhone(String phoneNum) {
        String regex = "^1[34589][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$";
        if (!(phoneNum.matches(regex))) {
            log.info("你输入的电话号码不正确");
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST, "你输入的电话号码不正确");
        }
    }
}
